import pygame
#import tkinter
import sys
import random


pic_0 = "pic\\0.png"
pic_2 = "pic\\2.png"
pic_4 = "pic\\4.png"
pic_8 = "pic\\8.png"
pic_16 = "pic\\16.png"
pic_32 = "pic\\32.png"
pic_64 = "pic\\64.png"
pic_128 = "pic\\128.png"
pic_256 = "pic\\256.png"
pic_512 = "pic\\512.png"
pic_1024 = "pic\\1024.png"
pic_2048 = "pic\\2048.png"
pic_win = "pic\\win.png"
pic_loss = "pic\\loss.png"


#每一个数据方块对应的类
class Block():
    def __init__(self,num):
        self.num = num
        self.pic = pic_0

    def update_pic(self,num):
        if(num == 0):
            self.pic = pic_0
        elif(num == 2):
            self.pic = pic_2
        elif(num == 4):
            self.pic = pic_4
        elif(num == 8):
            self.pic = pic_8
        elif(num == 16):
            self.pic = pic_16
        elif(num == 32):
            self.pic = pic_32
        elif(num == 64):
            self.pic = pic_64
        elif(num == 128):
            self.pic = pic_128
        elif(num == 256):
            self.pic = pic_256
        elif(num == 512):
            self.pic = pic_512
        elif(num == 1024):
            self.pic = pic_1024
        elif(num == 2048):
            self.pic = pic_2048

    def update_num(self,num):
        self.num = num
        self.update_pic(num)

    def show(self,screen,pos_x,pos_y):
        background = pygame.image.load(self.pic).convert()        
        screen.blit(background, (pos_x,pos_y))
        #这里不再刷新显示，以防止转置时把中间过程也显示出来
        #pygame.display.update()

#多个数据方块组成的棋盘
class Board():
    def __init__(self):
        self.row = 4
        self.col = self.row
        self.line_width = 4      
        self.line_color = 0xFFFFFF
        self.block_width = 106 + self.line_width*2
        self.m = 1
       
        
        #创建二维列表，元素为 Block 对象
        self.board = [[Block(0) for col in range(self.col)] for row in range(self.row)]
        
        self.screen = self.set_GUI()
        self.draw_fence()
        

    #根据数据方块的下标算出其画图的横坐标，注意横坐标是传列                
    def get_pos_x(self, col):
        return col*self.block_width+self.line_width
    
    #根据数据方块的下标算出其画图的纵坐标，注意纵坐标是传行 
    def get_pos_y(self, row):
        return row*self.block_width+self.line_width

   
    #以文本方式显示棋盘，调试用
    def show_board_data(self):
        for row in range(self.row):
            for col in range(self.col):
                print(str(self.board[row][col].num),end=" ")
            print("")
            
    #设置初始显示界面，返回对应句柄
    def set_GUI(self):                
        pygame.init()
        screen_width = self.block_width*self.col + self.line_width 
        screen_height = self.block_width*self.row + self.line_width 
        screen = pygame.display.set_mode((screen_width, screen_height))
        pygame.display.set_caption("Game-2048")
        return screen
       
    #设置棋盘的栅栏，即每个数据方块之间的间隔，并显示
    def draw_fence(self):        
        surface = pygame.display.get_surface()
        
        #画竖线
        for i in range(self.row+1):
            for j in range(self.col+1):
                #界面，白色，起始坐标，结束坐标，宽度
                pygame.draw.line(surface, self.line_color, (j*self.block_width, i*self.block_width), (j*self.block_width, (i+1)*self.block_width), self.line_width)
                
        #画横线
        for i in range(self.col+1):
            for j in range(self.row+1):
                #界面，白色，起始坐标，结束坐标，宽度
                pygame.draw.line(surface, self.line_color, (j*self.block_width, i*self.block_width), ((j+1)*self.block_width, i*self.block_width), self.line_width)

        #更新显示
        #pygame.display.update()                

    #游戏开盘时，随机产生一个数据方块
    def first_rand(self):
        #注意是从0开始，所以下标不需要做处理
        rand = random.randint(0, (self.row * self.col) - 1)
       # print("first_rand: " + str(rand)+" row: "+str(rand // self.row) + " col: " + str(rand % self.col))
        first_block_row = rand // self.col
        first_block_col = rand % self.row
        first_block = self.board[first_block_row][first_block_col]
        first_block.update_num(2)
        #第一次画的时候，第一根线占了屏幕边缘，所有后面画的都要也加上这个偏移        
        first_block.show(self.screen, self.get_pos_x(first_block_col), self.get_pos_y(first_block_row))

        #初始化其它为0的数据广场的图片显示
        for row in range(self.row):
            for col in range(self.col):
                 block = self.board[row][col]
                 self.board[row][col].show(self.screen, self.get_pos_x(col), self.get_pos_y(row))
        #刷新显示
        pygame.display.update()
        
    #随机一个位置生成方块2，当没有位置生成时，游戏结束          
    def new_rand(self):
        index_list = []

        #判断版面是否已满，满则游戏失败
        full_flag = True
      
        for row in range(self.row):
            for col in range(self.col):
                if(self.board[row][col].num == 0):
                    #print("row="+str(row)+",col="+str(col)+",val="+str(row*self.bd.col + col))
                    index_list.append(row*self.col + col)
                    full_flag = False

        if(full_flag == True):
            return False
        rand_index = random.choice(index_list)
        rand_row = rand_index // self.col
        rand_col = rand_index % self.row
        rand_block = self.board[rand_row][rand_col]
        rand_block.update_num(2)
        rand_block.show(self.screen, self.get_pos_x(rand_col), self.get_pos_y(rand_row))
       
        pygame.display.update()		

    #完整显示棋盘		
    def show_board_all(self):
        for row in range(self.row):
            for col in range(self.col):
                self.board[row][col].show(self.screen, self.get_pos_x(col), self.get_pos_y(row))
        #更新显示
        pygame.display.update()     

    #棋盘数据清零
    def clear_board(self):
        for row in range(self.row):
            for col in range(self.col):
                block = self.board[row][col]                
                block.update_num(0)
                    
    #顺时针旋转
    def clockwise_trans(self):
        temp = [[0 for col in range(self.col)] for row in range(self.row)]
        #转存数据暂存
        for row in range(self.row):
            for col in range(self.col):
                temp[row][col] = self.board[self.row-1-col][row].num
        #再换回来                
        for row in range(self.row):
            for col in range(self.col):
                self.board[row][col].update_num(temp[row][col])
                
        #转置时不显示，以免造成显示混乱       
        #self.show_board_all()
        return self.board

    #统计棋盘中数据为0的格子的数量 
    def cnt_of_not_0(self):
        cnt = 0
        for row in range(self.row):
            for col in range(self.col):
                if(self.board[row][col].num == 0):
                    cnt = cnt + 1
        return cnt           
        
#游戏类
class Game():
    def __init__(self):
        self.bd = Board()
        #当数据进行运算合并时，运算过程中不能显示到板面，否则数据会跳跃混乱显示，因此定义一个暂存的棋盘，当运算完成时，再刷新显示到真正的棋盘
        self.bd_temp = Board()
        self.game_loss = False
        self.game_win = False
        
        #如果按键什么都没有做，则此标志不变，这种情况下要产生随机方块
        self.do_nothing_flag = 1

    def you_win(self,bd):
        print("************")
        print("*** YOU  ***")
        print("*** WIN  ***")
        print("************")
        print("************")
        self.game_win = True
        background = pygame.image.load(pic_win).convert()        
        bd.screen.blit(background, (bd.block_width*0.5,bd.block_width*1.5))
        pygame.display.update()

    def you_loss(self,bd):
        print("************")
        print("*** YOU  ***")
        print("*** LOSS ***")
        print("************")
        print("************")
        self.game_loss = True
        background = pygame.image.load(pic_loss).convert()        
        bd.screen.blit(background, (15,100))
        pygame.display.update()
        
    #开始游戏    
    def run(self,bd):
        bd.first_rand()
        self.listen_key()

        
    #键盘监听   
    def listen_key(self):        
        while True:
            for event in pygame.event.get():
                if(event.type == pygame.QUIT):
                    print("QUIT")
                    pygame.quit()
                    sys.exit()
                if(event.type == pygame.KEYDOWN):
                    if(event.key == pygame.K_LEFT):
                        print("K_LEFT")
                        if(1 == self.move_left(self.bd)):
                            #如果没有进行任何操作，返回1，则应该产生随机方块，下同,如果产生随机方块失败，则游戏结束
                            if(self.bd.new_rand() == False):
                                 self.game_loss = True                                
                    elif(event.key == pygame.K_RIGHT):
                        print("K_RIGHT")
                        if(1 == self.move_right(self.bd)):
                            if(self.bd.new_rand() == False):
                                 self.game_loss = True
                    elif(event.key == pygame.K_UP):
                        print("K_UP")
                        if(1 == self.move_up(self.bd)):
                            if(self.bd.new_rand() == False):
                                 self.game_loss = True
                    elif(event.key == pygame.K_DOWN):
                        print("K_DOWN")                                               
                        if(1 == self.move_down(self.bd)):
                            if(self.bd.new_rand() == False):
                                 self.game_loss = True
                    self.bd.show_board_all()
                    pygame.display.update()

                #跳出for循环    
                if(self.game_win == True or self.game_loss == True):                    
                    break                
            #跳出while循环        
            if(self.game_win == True):
                self.you_win(self.bd)
                break
            
            if(self.game_loss == True):
                self.you_loss(self.bd)
                break
            
        #游戏结束后，只监听退出键
        while True:
             for event in pygame.event.get():
                if(event.type == pygame.QUIT):
                    print("OVER")
                    pygame.quit()
                    sys.exit()

#--------------------------------------------------------
##             处理方法：            
##            1、先从后往前遍历此行，依次往前移，把空格填充完
##            2、然后再从后往前合并，然后重复步骤1（此时）

#步骤一：先从后往前遍历此行，依次往前移，把空格填充完            
    #返回 do_nothing_flag  的状态
    def move_left(self,bd):
        print("K_LEFT")

        #统计非0方块数
        cnt_old = bd.cnt_of_not_0()
        nothing_to_do_flag = 1
        #统计有多少行是全空的
        empty_line_cnt = 0
        for row in range(bd.row):
            #统计此行数据块为0（即空数据块）的个数
            col = 0
            while( col<bd.col and bd.board[row][col].num==0 ):
                col = col+1
                
            #到末尾了仍然没有找到，则为空行 ，则跳过此行的处理 
            if( col == bd.col ):               
                empty_line_cnt = empty_line_cnt+1
                continue
            
            #全部为空行，未进行任何操作，返回标志，以产生随机方块
            if(empty_line_cnt == bd.row):
                do_nothing_flag = 1
                return do_nothing_flag
            
            #到这里，表明至少有一个非空格元素，此时要：找到第一个空数据块，将其后元素往前依次移动一格
            #本行空格的数量
            empty_block_cnt = 0
            col = 0
            while( col<bd.col ):
                if( bd.board[row][col].num==0 ):
                    empty_block_cnt = empty_block_cnt+1
                col = col +1           

            #如果 col 为4，即empty_block_cnt为0，此行是满的，没有空数据，暂未处理

            #开始将空数据块后面的方块依次往前移    
            cnt = 0
            while(cnt<empty_block_cnt):
                col = 0
                #先定位到第一个空数据块
                while( col<bd.row and bd.board[row][col].num!=0 ):
                    col = col+1
                    
                #开始移动
                for i in range(col, bd.col-1):
                   
                    if(bd.board[row][i+1].num != 0):
                        bd.board[row][i].update_num(bd.board[row][i+1].num)
                        bd.board[row][i].show(bd.screen, bd.get_pos_x(i), bd.get_pos_y(row))
                        bd.board[row][i+1].update_num(0)
                        bd.board[row][i+1].show(bd.screen, bd.get_pos_x(i+1), bd.get_pos_y(row))
                        nothing_to_do_flag = 0
                cnt = cnt + 1
              
        #进行过操作后（并且数据块的数量变了），其它操作就不进行了，应当返回
        if(nothing_to_do_flag == 0 and cnt_old != bd.cnt_of_not_0()):           
            return nothing_to_do_flag
            
                        
#步骤二：上面把空格都填充完了，现在开始合并
        nothing_to_do_flag = 1
        for row in range(bd.row):
            #遍历列
            for col in range(bd.col-1,0,-1):
                block = bd.board[row][col]
                if(bd.board[row][col].num!=0 and bd.board[row][col-1].num!=0 and bd.board[row][col].num==bd.board[row][col-1].num):
                    old_val = bd.board[row][col-1].num
                    #往前合并
                    bd.board[row][col-1].update_num(old_val*2)
                    bd.board[row][col-1].show(bd.screen, bd.get_pos_x(col-1), bd.get_pos_y(row))
                    #本格更新为空格
                    bd.board[row][col].update_num(0)
                    bd.board[row][col].show(bd.screen, bd.get_pos_x(col), bd.get_pos_y(row))
  
                    nothing_to_do_flag = 0

                    #游戏胜利
                    if(old_val*2 == 2048):
                        self.game_win == True
 
                    

        #进行过操作后，其它操作就不进行了，应当返回
        if(nothing_to_do_flag == 0):
            return nothing_to_do_flag
        
        return nothing_to_do_flag

    #上移操作，通过转置，然后借用左移的方法实现
    def move_up(self,bd):
        nothing_to_do_flag = 1
        
        #将数据复制到临时版面中操作，以免操作过程中跳闪    
        self.bd_temp.clear_board()              
        self.copy_data(self.bd_temp, self.bd)        

        #复制数据到temp，并进行合并操作       
        self.bd_temp.clockwise_trans()     
        self.bd_temp.clockwise_trans()  
        self.bd_temp.clockwise_trans()
        nothing_to_do_flag = self.move_left(self.bd_temp)  
        self.bd_temp.clockwise_trans()        

        #操作完成后，将数据复制回来，并进行显示             
        self.copy_data(self.bd, self.bd_temp)  
        return nothing_to_do_flag

    #下移操作，通过转置，然后借用左移的方法实现
    def move_down(self,bd):
        nothing_to_do_flag = 1
          
        self.bd_temp.clear_board()              
        self.copy_data(self.bd_temp, self.bd)        
   
        self.bd_temp.clockwise_trans()         
        nothing_to_do_flag = self.move_left(self.bd_temp)    
        self.bd_temp.clockwise_trans()  
        self.bd_temp.clockwise_trans()
        self.bd_temp.clockwise_trans()        
          
        self.copy_data(self.bd, self.bd_temp)  
        return nothing_to_do_flag

    #右移操作，通过转置，然后借用左移的方法实现
    def move_right(self,bd):
        nothing_to_do_flag = 1
         
        self.bd_temp.clear_board()              
        self.copy_data(self.bd_temp, self.bd)        
     
        self.bd_temp.clockwise_trans()        
        self.bd_temp.clockwise_trans()
        nothing_to_do_flag = self.move_left(self.bd_temp) 
        self.bd_temp.clockwise_trans()
        self.bd_temp.clockwise_trans()        
  
        self.copy_data(self.bd, self.bd_temp)  
        return nothing_to_do_flag
    
    
    #用于两个棋盘的数据复制    
    def copy_data(self,bd_to,bd_from):
        for row in range(bd_to.row):
            for col in range(bd_to.col):
                bd_to.board[row][col].update_num(bd_from.board[row][col].num)
        

#生成一个游戏并运行
game = Game()
game.run(game.bd)



